#region (c) 2014 THN Solutions LLC. - All rights reserved
/*
THN TELEPORT version 2

Copyright (c) 2014, THN Solutions LLC. ( www.ThnSolutions.com )
Author: Nguyễn, M. Hải                 ( www.Minh-Hai.com     )
All rights reserved.

This library is dual-licensed.
  + For commercial software, please obtain a commercial license from THN Solutions LLC.

  + For free software, this library is licensed under GPL version 3. A summary of GPLv3 is
    listed below. You should also find a copy of GPLv3 as file License_GPLv3.txt included
	with the source-code files.

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License version 3 as published by
    the Free Software Foundation  of the License.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#endregion

#region Using directives
using System;
using System.Net;
using System.Reflection;
using System.IO;
using System.CodeDom.Compiler;

using Microsoft.CSharp;

using Thn.Application.Configuration;
#endregion

namespace Thn.Communication.Teleporting.Connectors.WebService
{
    /// <summary>
    /// An automatic connector for Teleport using WebService
    /// </summary>
    public class WebServiceConnector : ITeleportConnector
    {
        #region Default Policy
        /// <summary>
        /// Default policy
        /// </summary>
        private static ICertificatePolicy DefaultPolicy = null;
        #endregion

        #region Fields

        bool useDefaultWebService = false;

        /// <summary>
        /// Whether SSL is enabled.
        /// </summary>
        bool mIsSSLEnabled = false;

        /// <summary>
        /// Certificate policy for SSL connections.
        /// <para>Default is SSLCertificatePolicy.Default.</para>
        /// </summary>
        SSLCertificatePolicy mCertificatePolicy = SSLCertificatePolicy.Default;

        /// <summary>
        /// Custom certificate policy.
        /// </summary>
        ICertificatePolicy mCustomCertificatePolicy = null;

        /// <summary>
        /// WebService Proxy
        /// </summary>
        IWebServiceProxy proxy = null;

        #region File template
        /// <summary>
        /// File template
        /// </summary>
        string TemplateFileNameProxy = "Thn.Communication.Teleporting.Connectors.WebService.ProxyTemplate.txt";

        /// <summary>
        /// Template Namespace using replace Namespace
        /// </summary>
        string TemplateNamespace = "$Namespace$";

        /// <summary>
        /// Template MethodName using replace MethodName
        /// </summary>
        string TemplateMethodName = "$WebMethod$";
        #endregion
        
        #endregion        

        #region Properties

        #region Url
        private string mUrl = string.Empty;
        /// <summary>
        /// Gets/Sets URL of the webservice.
        /// </summary>
        public string Url
        {
            get { return mUrl; }
            set { mUrl = value; }
        }
        #endregion

        #region Method Name
        private string mMethod = "TeleportRequest";
        /// <summary>
        /// Gets/Sets name of the teleport method.
        /// </summary>
        public string Method
        {
            get { return mMethod; }
            set { mMethod = value; }
        }
        #endregion

        #region Xml Namespace
        private string mXmlNamespace = "http://tempuri.org/";
        /// <summary>
        /// Gets/Sets XML namespace.
        /// </summary>
        public string XmlNamespace
        {
            get { return mXmlNamespace; }
            set { mXmlNamespace = value; }
        }
        #endregion

        #endregion
        
        #region Teleport - Send message
        string tp_SendMessage(string gateway, string method, string encodedArgs)
        {
            string result = string.Empty;
            Exception error = null;
            try
            {
                //obtain the proxy object
                if (proxy == null) proxy = BuildProxy();

                // change the way to call ssl
                if (mIsSSLEnabled)
                {
                    if (mCertificatePolicy == SSLCertificatePolicy.TrustAll)
                    {
                        System.Net.ServicePointManager.CertificatePolicy = mCustomCertificatePolicy;
                    }
                    else
                    {
                        System.Net.ServicePointManager.CertificatePolicy = DefaultPolicy;
                    }
                }
                #region defensive
                else
                {
                    // do nothing when not SSL connection.
                }
                #endregion

                //send the message using web service
                result = proxy.TeleportRequest(gateway, method, encodedArgs);
            }
            catch (Exception ex)
            {
                error = ex;
            }
            finally
            {
                if (error != null)
                    Log.Error(error);
            }
            return result;
        }
        #endregion

        #region Build proxy

        IWebServiceProxy BuildProxy()
        {
            IWebServiceProxy result = null;

            if (useDefaultWebService) result = new DefaultWebServiceProxy();
            else
            {
                //Generate and compile dynamic proxy here
                string code = string.Empty;

                #region Read text from Resource

                code = ReadProxyTemplate(TemplateFileNameProxy);
               
                #endregion

                #region Replace
                //Replace Template Namespace
                code = code.Replace(TemplateNamespace, mXmlNamespace);

                //Replace Template Namespace
                code = code.Replace(TemplateMethodName, mMethod);
                #endregion

                #region create instance
                if (code != string.Empty)
                {
                    result = GetInstanceFromContent(code);
                }
                else
                {
                    result = new DefaultWebServiceProxy();
                }
                #endregion
            }

            #region Initialize settings
            //assign the corresponding URL obtained from config file (Teleport.xml)
            result.Url = mUrl;

            // save a backup to default policy
            if (DefaultPolicy == null)
            {
                DefaultPolicy = System.Net.ServicePointManager.CertificatePolicy;
            }
            #region Defensive
            else
            {
                // it has backup already, no need to backup anymore
            }
            #endregion

            if (mCertificatePolicy == SSLCertificatePolicy.TrustAll)
            {
                mCustomCertificatePolicy = new TrustAllCertificatePolicy();
            }
            #region Defensive
            else
            {
                // default policy do not need to change
            }
            #endregion

            #endregion

            return result;
        }

        #region Compile code to assembly
        /// <summary>
        /// Write contain of class to Temporary file
        /// And Compile Assembly From File
        /// Then Create Instance IWebServiceProxy
        /// </summary>
        /// <param name="contentFile">Code</param>
        private IWebServiceProxy GetInstanceFromContent(string contentFile)
        {
            IWebServiceProxy result = null;
            
            #region Build
            Assembly assembly = null;
            CSharpCodeProvider cscp = new CSharpCodeProvider();
            
            CompilerResults compilerResult = cscp.CompileAssemblyFromSource(GetCompilerParameters(), contentFile); 
            if (compilerResult.Errors.Count == 0)
            {
                //Log.Debug("No errors while compilation.");
                assembly = compilerResult.CompiledAssembly;
            }
            else
            {
                Log.Debug("There are errors:");
                foreach (System.CodeDom.Compiler.CompilerError error in compilerResult.Errors)
                {
                    Log.Debug(string.Format("Line {0} col {1}: {2}", error.Line, error.Column, error.ErrorText));
                }
            }
            #endregion

            #region Create Instance

            if (assembly != null)
            {
                Type[] types = assembly.GetTypes();
                foreach (Type t in types)
                {
                    if (t.GetInterface("IWebServiceProxy") != null)
                    {
                        result = Activator.CreateInstance(t) as IWebServiceProxy;
                    }
                }
            }
            #endregion

            return result;
        }
        #endregion

        #region Get Compiler Parameters
        /// <summary>
        /// Get the parameters used to invoke compiler
        /// </summary>
        /// <returns></returns>
        private CompilerParameters GetCompilerParameters()
        {
            CompilerParameters result = new CompilerParameters();
            // Generate our assembly in memory
            result.GenerateInMemory = true; 
           
            // Get the location of all other assemblies that ours reference
            Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
            foreach (Assembly assembly in assemblies)
            {
                // Add each assembly
                result.ReferencedAssemblies.Add(assembly.Location);
            }

            return result;
        }
        #endregion


        #region Read Proxy Template from Resource
        string ReadProxyTemplate(string fileName)
        {
            string result = string.Empty;
            //Get ManifestResource Stream 
            Assembly assembly = this.GetType().Assembly;
            Stream stream = assembly.GetManifestResourceStream(fileName);
            byte[] buffer = new byte[Int16.MaxValue];
            //Read all file
            stream.Read(buffer, 0, (int)stream.Length);
            result = MessageConversion.ByteArrayToStringUTF8(buffer);
            //
            return result;
        }
        #endregion


        #endregion

        #region Apply Configuration
        /// <summary>
        /// Read parameters from configuration.
        /// </summary>
        void ApplyConfiguration(ConfigSection section)
        {
            //Url
            string tmp = section["Url"];
            if ((tmp != null) && (tmp != string.Empty)) mUrl = tmp;

            //Method
            tmp = section["Method"];
            if ((tmp != null) && (tmp.Trim() != string.Empty)) mMethod = tmp;

            //Namespace
            tmp = section["XmlNamespace"];
            if ((tmp != null) && (tmp.Trim() != string.Empty)) mXmlNamespace = tmp;

            //check whether default webservice is enough to use
            useDefaultWebService = (mMethod == "TeleportRequest") && (mXmlNamespace == "http://tempuri.org/");

            #region check ssl
            string sslEnabled = section["SSLEnabled"];
            if (sslEnabled != null)
            {
                sslEnabled = sslEnabled.ToLower();
                // if ssl is enabled
                if (sslEnabled.Equals("true"))
                {
                    // turn it on ssl enabled.
                    mIsSSLEnabled = true;

                    //continue to read SSL Certificate policy
                    string sslCertificate = section["SSLCertificate"];
                    if (sslCertificate != null)
                    {
                        sslCertificate = sslCertificate.ToLower();
                        if (sslCertificate.Equals("default"))
                        {
                            this.mCertificatePolicy = SSLCertificatePolicy.Default;
                        }
                        else if (sslCertificate.Equals("trustall"))
                        {
                            this.mCertificatePolicy = SSLCertificatePolicy.TrustAll;
                        }
                        #region tracing
                        else
                        {
                            Log.Warning("Initialize web service unknown SSL Certificate:{0}", sslCertificate);
                        }
                        #endregion

                    }
                    #region Tracing
                    else
                    {
                        // not specify the certificate, get default.
                    }
                    #endregion
                }
                #region tracing
                else
                {
                    // not enabled.
                }
                #endregion
            }
            #region do nothing
            else
            {
                // ssl is not enabled.
            }
            #endregion
            #endregion
        }
        #endregion

        #region ITeleportConnector Members

        #region Teleporter
        private ITeleporter mTeleporter;
        /// <summary>
        /// Gets/Sets the teleporter this connector shall be connecting for.
        /// <para>When unassigned, the connector shall use the default teleporter.</para>
        /// </summary>
        public ITeleporter Teleporter
        {
            get
            {
                ITeleporter result = null;
                if (mTeleporter != null) result = mTeleporter;
                else result = Teleport.Teleporter;

                return result;
            }
            set { mTeleporter = value; }
        }
        #endregion

        #region Configuration
        private ConfigSection mConfiguration;
        /// <summary>
        /// Gets/Sets a custom configuration for initialization.
        /// </summary>
        public ConfigSection Configuration
        {
            get { return mConfiguration; }
            set
            {
                mConfiguration = value;
                if (mConfiguration != null) ApplyConfiguration(mConfiguration);
                else Thn.Exceptions.NullArgumentException.Publish("Configuration");
            }
        }
        #endregion

        #region Initialize
        /// <summary>
        /// Prepare this connector.
        /// </summary>
        public void Initialize()
        {
            //Get teleporter object
            Teleporter tp = (Teleporter)this.Teleporter;

            //Intercept send message event to process by sending the message to remoting
            tp.SendMessage += new TeleportRequestHandler(tp_SendMessage);
        }
        #endregion

        #endregion
    }
}
